/**
 * Copyright (C) 2014-2015 LinkedIn Corp. (pinot-core@linkedin.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.linkedin.pinot.tools.scan.query;

import com.linkedin.pinot.core.common.BlockMultiValIterator;
import com.linkedin.pinot.core.common.BlockSingleValIterator;
import com.linkedin.pinot.core.query.utils.Pair;
import com.linkedin.pinot.core.segment.index.ColumnMetadata;
import com.linkedin.pinot.core.segment.index.IndexSegmentImpl;
import com.linkedin.pinot.core.segment.index.SegmentMetadataImpl;
import com.linkedin.pinot.core.segment.index.readers.Dictionary;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.ArrayUtils;


public class Projection {
  private final IndexSegmentImpl _indexSegment;
  private final SegmentMetadataImpl _metadata;
  private final List<Integer> _filteredDocIds;
  private final List<Pair> _columnList;
  private final Set<String> _mvColumns;
  private final Map<String, int[]> _mvColumnArrayMap;
  private Map<String, Dictionary> _dictionaryMap;
  private boolean _addCountStar;
  private int _limit = 10;

  Projection(IndexSegmentImpl indexSegment, SegmentMetadataImpl metadata, List<Integer> filteredDocIds,
      List<Pair> columns, Map<String, Dictionary> dictionaryMap, boolean addCountStar) {

    _indexSegment = indexSegment;
    _metadata = metadata;
    _filteredDocIds = filteredDocIds;
    _dictionaryMap = dictionaryMap;
    _addCountStar = addCountStar;

    _columnList = new ArrayList<>();
    for (Pair pair : columns) {
      _columnList.add(pair);
    }

    _mvColumns = new HashSet<>();
    _mvColumnArrayMap = new HashMap<>();

    for (ColumnMetadata columnMetadata : _metadata.getColumnMetadataMap().values()) {
      String column = columnMetadata.getColumnName();

      if (!columnMetadata.isSingleValue()) {
        _mvColumns.add(column);
      }
      _mvColumnArrayMap.put(column, new int[columnMetadata.getMaxNumberOfMultiValues()]);
    }
  }

  public ResultTable run() {
    ResultTable resultTable = new ResultTable(_columnList, _filteredDocIds.size());
    resultTable.setResultType(ResultTable.ResultType.Selection);

    for (Pair pair : _columnList) {
      String column = (String) pair.getFirst();
      if (!_mvColumns.contains(column)) {
        BlockSingleValIterator bvIter =
            (BlockSingleValIterator) _indexSegment.getDataSource(column).getNextBlock().getBlockValueSet().iterator();

        int rowId = 0;
        for (Integer docId : _filteredDocIds) {
          bvIter.skipTo(docId);
          resultTable.add(rowId++, bvIter.nextIntVal());
        }
      } else {
        BlockMultiValIterator bvIter =
            (BlockMultiValIterator) _indexSegment.getDataSource(column).getNextBlock().getBlockValueSet().iterator();

        int rowId = 0;
        for (Integer docId : _filteredDocIds) {
          bvIter.skipTo(docId);
          int[] dictIds = _mvColumnArrayMap.get(column);
          int numMVValues = bvIter.nextIntVal(dictIds);

          dictIds = Arrays.copyOf(dictIds, numMVValues);
          resultTable.add(rowId++, ArrayUtils.toObject(dictIds));
        }
      }
    }

    return transformFromIdToValues(resultTable, _dictionaryMap, _addCountStar);
  }

  public ResultTable transformFromIdToValues(ResultTable resultTable, Map<String, Dictionary> dictionaryMap,
      boolean addCountStar) {
    List<Pair> columnList = resultTable.getColumnList();

    for (ResultTable.Row row : resultTable) {
      int colId = 0;
      for (Object object : row) {
        String column = (String) columnList.get(colId).getFirst();
        Dictionary dictionary = dictionaryMap.get(column);

        if (object instanceof Object[]) {
          Object[] objArray = (Object[]) object;
          Object[] valArray = new Object[objArray.length];

          for (int i = 0; i < objArray.length; ++i) {
            int dictId = (int) objArray[i];
            valArray[i] = dictionary.get(dictId);
          }
          row.set(colId, valArray);
        } else {
          int dictId = (int) object;
          row.set(colId, dictionary.get(dictId));
        }
        ++colId;
      }
    }

    // Add additional column for count(*)
    if (addCountStar) {
      for (ResultTable.Row row : resultTable) {
        row.add(1);
      }
      resultTable.addCountStarColumn();
    }

    return resultTable;
  }
}
